package com.huan.es8.script;

import co.elastic.clients.elasticsearch._types.ScriptLanguage;
import co.elastic.clients.elasticsearch._types.mapping.IntegerNumberProperty;
import co.elastic.clients.elasticsearch._types.mapping.KeywordProperty;
import co.elastic.clients.elasticsearch._types.mapping.ObjectProperty;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.json.JsonData;
import com.huan.es8.AbstractEs8Api;
import org.junit.jupiter.api.*;

import java.io.IOException;
import java.util.Arrays;

/**
 * 脚本字段处理
 * <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-fields.html#script-fields">参考文档</a>
 * <a href="https://blog.csdn.net/fu_huo_1993/article/details/128810117">博客地址</a>
 * <p>
 * 需要注意， doc[..]和params['_source'][..]访问的区别，前者会把数据加载到缓存中，访问速度快，但是不可处理json类型，比如nested类型，
 * 后者每次访问_source都会进行解析，性能稍低，但是可以处理复杂类型
 *
 * @author huan.fu
 * @date 2023/1/31 - 12:16
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ScriptFieldApi extends AbstractEs8Api {

    private final String INDEX_NAME = "index_script_fields";

    @BeforeAll
    public void createIndex() throws IOException {
        client.indices()
                .create(indexRequest ->
                        indexRequest.index(INDEX_NAME)
                                .mappings(mappings ->
                                        mappings.properties("name", new Property(new KeywordProperty.Builder().build()))
                                                .properties("sex", new Property(new IntegerNumberProperty.Builder().build()))
                                                .properties("hobbies", new Property(new KeywordProperty.Builder().build()))
                                                .properties("address", new Property(
                                                        new ObjectProperty.Builder()
                                                                .properties("province", new Property(new KeywordProperty.Builder().build()))
                                                                .properties("city", new Property(new KeywordProperty.Builder().build()))
                                                                .build()
                                                ))
                                )
                );
        bulk(INDEX_NAME, Arrays.asList(
                "{\"name\":\"张三\",\"sex\":1,\"hobbies\":[\"足球\",\"篮球\"],\"address\":{\"province\":\"湖北\",\"city\":\"city01\"}}",
                "{\"name\":\"张三\",\"sex\":2,\"address\":{\"province\":\"北京\",\"city\":\"city01\"}}",
                "{\"name\":\"张三\",\"hobbies\":[\"足球\"],\"address\":{\"province\":\"湖北\",\"city\":\"city01\"}}"
        ));
    }

    @Test
    @DisplayName("格式化性别 1-男 2-女 -1-未知 如果不存在sex字段，则显示-- 其余的显示 **")
    public void test01() throws IOException {
        SearchRequest request = SearchRequest.of(searchRequest ->
                searchRequest.index(INDEX_NAME)
                        .query(query -> query.matchAll(matchAll -> matchAll))
                        // 不加这句，则 _source 不会返回，值返回 fields
                        .source(config -> config.filter(filter -> filter.includes("*")))
                        .scriptFields("sex_format", field ->
                                field.script(script ->
                                        script.inline(inline ->
                                                inline.lang(ScriptLanguage.Painless)
                                                        .source(" // 判断 sex 字段是否存在\n" +
                                                                "          if(doc['sex'].size() == 0){\n" +
                                                                "            return \"--\";\n" +
                                                                "          }\n" +
                                                                "        \n" +
                                                                "          if(doc['sex'].value == 1){\n" +
                                                                "            return \"男\";\n" +
                                                                "          }else if(doc['sex'].value == 2){\n" +
                                                                "            return \"女\";\n" +
                                                                "          }else if(doc['sex'].value == -1){\n" +
                                                                "            return \"未知\";\n" +
                                                                "          }else{\n" +
                                                                "            return \"**\";\n" +
                                                                "          }")
                                        )
                                )
                        )
                        .size(100)
        );

        System.out.println("request: " + request);
        SearchResponse<Object> response = client.search(request, Object.class);
        System.out.println("response: " + response);
    }

    @Test
    @DisplayName("判断用户是否有某个爱好")
    public void test02() throws IOException {
        SearchRequest request = SearchRequest.of(searchRequest ->
                searchRequest.index(INDEX_NAME)
                        .query(query -> query.matchAll(matchAll -> matchAll))
                        // 不加这句，则 _source 不会返回，值返回 fields
                        .source(config -> config.filter(filter -> filter.includes("*")))
                        .scriptFields("has_hobby", field ->
                                field.script(script ->
                                        script.inline(inline ->
                                                inline.lang(ScriptLanguage.Painless)
                                                        .source(" // 没有hobbies字段，直接返回 false\n" +
                                                                "          if(doc['hobbies'].size() == 0){\n" +
                                                                "            return false;\n" +
                                                                "          }\n" +
                                                                "          return doc['hobbies'].indexOf(params.hobby) > -1;")
                                                        .params("hobby", JsonData.of("篮球"))
                                        )
                                )
                        )
                        .size(100)
        );

        System.out.println("request: " + request);
        SearchResponse<Object> response = client.search(request, Object.class);
        System.out.println("response: " + response);
    }

    @Test
    @DisplayName("统计湖北省下的用户有几个")
    public void test03() throws IOException {
        SearchRequest request = SearchRequest.of(searchRequest ->
                searchRequest.index(INDEX_NAME)
                        .query(query -> query.matchAll(matchAll -> matchAll))
                        // 不加这句，则 _source 不会返回，值返回 fields
                        .source(config -> config.filter(filter -> filter.includes("*")))
                        .aggregations("agg_province", agg ->
                                agg.sum(sum ->
                                        sum.script(script ->
                                                script.inline(inline ->
                                                        inline.lang(ScriptLanguage.Painless)
                                                                // 因为 address 是一个复杂类型，因此不可直接通过 doc 来访问, 只可通过 params['_source']来访问
                                                                .source("// 因为 address 是一个复杂类型，因此不可直接通过 doc 来访问\n" +
                                                                        "            if(params['_source']['address']['province'] == '湖北'){\n" +
                                                                        "              return 1;\n" +
                                                                        "            }\n" +
                                                                        "            return 0;")
                                                )
                                        )
                                )
                        )
                        .size(100)
        );

        System.out.println("request: " + request);
        SearchResponse<Object> response = client.search(request, Object.class);
        System.out.println("response: " + response);
    }

    @AfterAll
    public void deleteIndex() throws IOException {
        deleteIndex(INDEX_NAME);
    }
}
